/*
 * Copyright 2011 Katsunori Koyanagi
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package sweezy.swf.tools.abcdump
{
	import sweezy.core.utils.StringUtils;
	import sweezy.swf.abc.Opcode;
	import sweezy.swf.abc.Opcodes;
	import sweezy.swf.abc.opcode.Lookupswitch;
	import sweezy.swf.abc.opcode.Newclass;
	import sweezy.swf.abc.opcode.Newfunction;
	import sweezy.swf.abc.structure.ExceptionInfo;
	import sweezy.swf.abc.structure.InstanceInfo;
	import sweezy.swf.abc.structure.MethodBodyInfo;
	import sweezy.swf.abc.structure.MethodInfo;
	import sweezy.swf.abc.structure.TraitInfo;

	internal class CodeDef extends ABCElementDef
	{

		private var _methodBodyInfo:MethodBodyInfo;

		public function CodeDef(container:ABCcontainerDef, methodBodyInfo:MethodBodyInfo)
		{
			super(container);

			_methodBodyInfo = methodBodyInfo;
		}

		override public function dump(stream:IDumpStream):void
		{
			dumpActivation(stream);

			var opcodes:Array = this.opcodes;
			stream.println("//local_count=" + _methodBodyInfo.localCount + ", max_scope=" + _methodBodyInfo.maxScopeDepth + ", max_stack=" + _methodBodyInfo.maxStack + ", code_len=" + _methodBodyInfo.codeLength + ", code_count=" + opcodes.length);
			for each (var exception:ExceptionInfo in _methodBodyInfo.exceptionInfos)
			{
				stream.println("//exception from=" + exception.codeIndexFrom + ", to=" + exception.codeIndexTo + ", target=" + exception.target + ", var_name=" + exception.varName + ", type=" +
					exception.exceptionType);
			}


			if (!("size" in stream.context))
			{
				stream.context.size = 0;
			}

			var closures:Object = getClosureFunctionDefs();
			var labels:Labels = new Labels();

			for (var i:uint = 0, len:uint = opcodes.length; i < len; i++)
			{
				var opcode:Opcode = opcodes[i];
				var next:Opcode = opcodes[i + 1];

				if (labels.hasLabel(opcode))
				{
					stream.println();
					stream.println(labels.getLabel(opcode.position) + ":");
				}

				stream.println(opcodeToString(opcode, next, labels));
				if (opcode is Newfunction)
				{
					var functionDef:FunctionDef = closures[(opcode as Newfunction).index];
					if (functionDef !== null)
					{
						stream.println();
						stream.indentLevel++;
						functionDef.dump(stream);
						stream.indentLevel--;
						stream.println();
					}
				}

				if (!(opcode.code in stream.context))
				{
					stream.context[opcode.code] = 0;
				}
				stream.context[opcode.code] += opcode.size;
				stream.context.size += opcode.size;
			}
		}

		private function dumpActivation(stream:IDumpStream):void
		{
			if (_methodBodyInfo.traitInfos.length === 0)
			{
				return;
			}

			stream.println("//activation");
			stream.println("{");
			stream.indentLevel++;

			for each (var trait:TraitInfo in _methodBodyInfo.traitInfos)
			{
				new VariableDef(_container, trait, false).dump(stream);
			}

			stream.indentLevel--;
			stream.println("}");
		}

		private function getClosureFunctionDefs():Object
		{
			var result:Object = {};
			for each (var opcode:Opcode in _methodBodyInfo.opcodes)
			{
				if (opcode.code === Opcodes.OP_newfunction)
				{
					var functionDef:FunctionDef = new FunctionDef(_container, null, opcode, false);
					result[functionDef.methodIndex] = functionDef;
				}
			}

			return result;
		}

		private function opcodeToString(opcode:Opcode, next:Opcode, labels:Labels):String
		{
			var newLine:Boolean = false;
			var args:Array = [];
			var result:Array = [];
			result[0] = (opcode.position + "         ").substring(0, 10);
			result[1] = (opcode.name + "               ").substring(0, 17);

			switch (opcode.code)
			{
				case Opcodes.OP_ifeq:
				case Opcodes.OP_iffalse:
				case Opcodes.OP_ifge:
				case Opcodes.OP_ifgt:
				case Opcodes.OP_ifle:
				case Opcodes.OP_iflt:
				case Opcodes.OP_ifne:
				case Opcodes.OP_ifnge:
				case Opcodes.OP_ifngt:
				case Opcodes.OP_ifnle:
				case Opcodes.OP_ifnlt:
				case Opcodes.OP_ifstricteq:
				case Opcodes.OP_ifstrictne:
				case Opcodes.OP_iftrue:
				case Opcodes.OP_jump:
					args[0] = labels.getLabel(opcode.position + 4 + opcode["offset"]);
					args[1] = new Tuple("offset", opcode["offset"]);

					if (!labels.hasLabel(next))
					{
						newLine = true;
					}
					break;
				case Opcodes.OP_setsuper:
				case Opcodes.OP_getsuper:
				case Opcodes.OP_astype:
				case Opcodes.OP_coerce:
				case Opcodes.OP_getdescendants:
				case Opcodes.OP_istype:
				case Opcodes.OP_finddef:
				case Opcodes.OP_findproperty:
				case Opcodes.OP_findpropstrict:
				case Opcodes.OP_getlex:
				case Opcodes.OP_getproperty:
				case Opcodes.OP_deleteproperty:
				case Opcodes.OP_initproperty:
				case Opcodes.OP_setproperty:
					args[0] = Utils.multinameToString(_constantPool, opcode["index"]);
					break;
				case Opcodes.OP_pushint:
					args[0] = _constantPool.integerConstants[opcode["index"]];
					break;
				case Opcodes.OP_pushuint:
					args[0] = _constantPool.unsignedIntegerConstants[opcode["index"]];
					break;
				case Opcodes.OP_pushdouble:
					args[0] = _constantPool.doubleConstants[opcode["index"]];
					break;
				case Opcodes.OP_pushnamespace:
					args[0] = Utils.namespaceToString(_constantPool, opcode["index"]);
					break;
				case Opcodes.OP_dxns:
				case Opcodes.OP_debugfile:
				case Opcodes.OP_pushstring:
					args[0] = StringUtils.escapeESString(_constantPool.stringConstants[opcode["index"]]);
					break;
				case Opcodes.OP_pushshort:
					args[0] = opcode["shortValue"];
					break;
				case Opcodes.OP_pushbyte:
					args[0] = opcode["byteValue"];
					break;
				case Opcodes.OP_getglobalslot:
				case Opcodes.OP_getslot:
				case Opcodes.OP_setglobalslot:
				case Opcodes.OP_setslot:
					args[0] = new Tuple("slot", opcode["slotIndex"]);
					break;
				case Opcodes.OP_callproperty:
				case Opcodes.OP_callproplex:
				case Opcodes.OP_callsuper:
				case Opcodes.OP_callsupervoid:
				case Opcodes.OP_callpropvoid:
				case Opcodes.OP_constructprop:
					args[0] = Utils.multinameToString(_constantPool, opcode["index"]);
					args[1] = new Tuple("argCount", opcode["argCount"]);
					break;

				case Opcodes.OP_inclocal:
				case Opcodes.OP_inclocal_i:
				case Opcodes.OP_declocal:
				case Opcodes.OP_declocal_i:
				case Opcodes.OP_getlocal:
				case Opcodes.OP_setlocal:
				case Opcodes.OP_kill:
					args[0] = new Tuple("local", opcode["index"]);
					break;
				case Opcodes.OP_getscopeobject:
					args[0] = new Tuple("local stack", opcode["index"]);
					break;
				case Opcodes.OP_applytype:
				case Opcodes.OP_call:
				case Opcodes.OP_construct:
				case Opcodes.OP_constructsuper:
				case Opcodes.OP_newarray:
				case Opcodes.OP_newobject:
					args[0] = new Tuple("argCount", opcode["argCount"]);
					break;
				case Opcodes.OP_hasnext2:
					args[0] = new Tuple("local objectRegister", opcode["objectRegister"]);
					args[1] = new Tuple("local indexRegister", opcode["indexRegister"]);
					break;
				case Opcodes.OP_add_p:
				case Opcodes.OP_convert_m_p:
				case Opcodes.OP_decrement_p:
				case Opcodes.OP_divide_p:
				case Opcodes.OP_increment_p:
				case Opcodes.OP_modulo_p:
				case Opcodes.OP_multiply_p:
				case Opcodes.OP_negate_p:
				case Opcodes.OP_subtract_p:
					args[0] = new Tuple("param", opcode["param"]);
					break;
				case Opcodes.OP_declocal_p:
				case Opcodes.OP_inclocal_p:
					args[0] = new Tuple("param", opcode["param"]);
					args[1] = new Tuple("local", opcode["index"]);
					break;
				case Opcodes.OP_pushdecimal:
				case Opcodes.OP_pushuninitialized:
					args[0] = new Tuple("index?", opcode["index"]);
					break;
				case Opcodes.OP_debugline:
					args[0] = new Tuple("line", opcode["lineNumber"]);
					break;
				case Opcodes.OP_debug:
					args[0] = new Tuple("debugType", opcode["debugType"]);
					args[1] = new Tuple("index", opcode["index"]);
					args[2] = new Tuple("register", opcode["register"]);
					args[3] = new Tuple("extra", opcode["extra"]);
					break;
				case Opcodes.OP_lookupswitch:
					var lookupSwitch:Lookupswitch = opcode as Lookupswitch;
					var target:int = lookupSwitch.position + lookupSwitch.defaultOffset;
					args[0] = new Tuple("default", labels.getLabel(target));
					args[1] = new Tuple("cases", lookupSwitch.caseOffsets.length - 1);

					for each (var caseOffset:int in lookupSwitch.caseOffsets)
					{
						args[args.length] = labels.getLabel(caseOffset + lookupSwitch.position);
					}
					break;
				case Opcodes.OP_callstatic:
				case Opcodes.OP_callmethod:
					args[0] = new Tuple("argCount", opcode["argCount"]);

					var method:MethodInfo = _abc.methodInfos[opcode["index"]];
					if (method !== null)
					{
						args[1] = Utils.multinameToString(_constantPool, method.name);
					}
					break;
				case Opcodes.OP_newfunction:
					break;
				case Opcodes.OP_newcatch:
					args[0] = opcode["index"];
					break;
				case Opcodes.OP_newclass:
					var instance:InstanceInfo = _abc.instanceInfos[Newclass(opcode).index];

					if (instance !== null)
					{
						args[0] = Utils.multinameToString(_constantPool, instance.name);
					}
					break;
			}

			if (args.length > 0)
			{
				result[result.length] = "(" + args.join(", ") + ")";
			}
			return result.join(" ") + (newLine ? "\n" : "");
		}

		private function get opcodes():Array
		{
			return _methodBodyInfo.opcodes.slice();
		}
	}
}

import sweezy.swf.abc.Opcode;
import sweezy.swf.abc.Opcodes;
import sweezy.swf.abc.opcode.Label;

class Tuple
{

	private var _name:String;

	private var _value:*;

	public function Tuple(name:String, value:*)
	{
		_name = name;
		_value = value;
	}

	public function toString():String
	{
		return _name + "=" + _value;
	}
}

class Labels
{

	private var _count:uint;

	private var _values:Object;

	public function Labels()
	{
		_values = {};
	}

	public function getLabel(position:uint):String
	{
		if (!(position in _values))
		{
			_values[position] = "L" + ++_count;
		}

		return _values[position];
	}

	public function hasLabel(opcode:Opcode):Boolean
	{
		if (opcode === null)
		{
			return false;
		}

		return (opcode is Label) || (opcode.position in _values);
	}
}
